/********************************************************************************
 * Copyright (c) 2011-2017 Red Hat Inc. and/or its affiliates and others
 *
 * This program and the accompanying materials are made available under the 
 * terms of the Apache License, Version 2.0 which is available at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0 
 ********************************************************************************/
package org.eclipse.ceylon.model.loader.model;

import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.ceylon.model.loader.mirror.AnnotationMirror;
import org.eclipse.ceylon.model.typechecker.model.Class;


/**
 * Mirrors the elements of {@link java.lang.annotation.ElementType} for 
 * the purpose of targeting java annotations.
 */
public enum AnnotationTarget {

    TYPE {
        @Override
        public Set<OutputElement> outputs() {
            return Collections.singleton(OutputElement.TYPE);
        }
    },
    FIELD {
        @Override
        public Set<OutputElement> outputs() {
            return Collections.singleton(OutputElement.FIELD);
        }
    },
    METHOD {
        @Override
        public Set<OutputElement> outputs() {
            HashSet<OutputElement> result = new HashSet<OutputElement>(3);
            result.add(OutputElement.METHOD);
            result.add(OutputElement.GETTER);
            result.add(OutputElement.SETTER);
            return result;
        }
    },
    PARAMETER {
        @Override
        public Set<OutputElement> outputs() {
            return Collections.singleton(OutputElement.PARAMETER);
        }
    },
    CONSTRUCTOR {
        @Override
        public Set<OutputElement> outputs() {
            return Collections.singleton(OutputElement.CONSTRUCTOR);
        }
    },
    LOCAL_VARIABLE {
        @Override
        public Set<OutputElement> outputs() {
            return Collections.singleton(OutputElement.LOCAL_VARIABLE);
        }
    },
    ANNOTATION_TYPE {
        @Override
        public Set<OutputElement> outputs() {
            return Collections.singleton(OutputElement.ANNOTATION_TYPE);
        }
    },
    PACKAGE {
        @Override
        public Set<OutputElement> outputs() {
            return EnumSet.of(OutputElement.PACKAGE, OutputElement.TYPE);
        }
    },
    TYPE_USE {
        @Override
        public Set<OutputElement> outputs() {
            return Collections.emptySet();
        }
    },
    TYPE_PARAMETER {
        @Override
        public Set<OutputElement> outputs() {
            return Collections.emptySet();
        }
    },
    MODULE {
        @Override
        public Set<OutputElement> outputs() {
            // for now, because I don't think we can annotated java 9 module descriptors
            return Collections.emptySet();
        }
    };
    
    public abstract Set<OutputElement> outputs();
    
    /**
     * Returns the elements in the {@code @Target} annotation of the given 
     * {@code @interface}, or null if 
     * the annotation type lacks the {@code @Target} annotation.
     */
    public static EnumSet<AnnotationTarget> getAnnotationTarget(LazyInterface annotationType) {
        AnnotationMirror targetAnno = annotationType.classMirror.getAnnotation("java.lang.annotation.Target");
        if (targetAnno != null) {
            @SuppressWarnings("unchecked")
            List<String> targets = (List<String>)targetAnno.getValue();
            EnumSet<AnnotationTarget> result = EnumSet.noneOf(AnnotationTarget.class);
            for (String name : targets) {
                result.add(AnnotationTarget.valueOf(name));
            }
            return result;
        }
        return null;
    }
    
    /**
     * Returns the possible targets of the given annotation proxy class
     * (according to the {@code @Target} of the {@code @interface} that 
     * the class is a proxy to), 
     * or null if {@code @interface} lacks a {@code @Target} or if 
     * the given class is not an annotation proxy.
     */
    private static EnumSet<AnnotationTarget> annotationTargets(Class annotationClass) {
        if (annotationClass instanceof AnnotationProxyClass) {
            return getAnnotationTarget(((AnnotationProxyClass)annotationClass).iface);
        } else {
            return null;
        }
    }
    
    /**
     * Given a set of Java annotation constraints, returns the
     * possible Java elements that could be generated by Ceylon elements 
     * that the annotation could be applied to. 
     * @param targets
     * @return
     */
    private static EnumSet<OutputElement> possibleCeylonTargets(EnumSet<AnnotationTarget> targets) {
        if (targets == null) {
            targets = EnumSet.allOf(AnnotationTarget.class);
        }
        EnumSet<OutputElement> result = EnumSet.noneOf(OutputElement.class);
        for (AnnotationTarget t : targets) {
            result.addAll(t.outputs());
        }
        return result;
    }
    
    /**
     * The set of program elements the given 
     * annotation class could be applied to, according <strong>only</strong> to 
     * the {@code @Target}s on the corresponding {@code @interface}.
     * @param annotationClass
     * @return
     */
    public static EnumSet<OutputElement> outputTargets(Class annotationClass) {
        return possibleCeylonTargets(annotationTargets(annotationClass));
    }
}
